Skip to main content

Uniswap V3 闪电掉期套利

Uniswap V3 闪电掉期套利

Uniswap V3 闪存掉期套利示例

Uniswap V3 Flash Swap Arbitrage Uniswap V3 Flash Swap Arbitrage Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract UniswapV3FlashSwap {
ISwapRouter constant router =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);

uint160 internal constant MIN_SQRT_RATIO = 4295128739;
uint160 internal constant MAX_SQRT_RATIO =
1461446703485210103287273052203988822378723970342;

// Example WETH/USDC
// Sell WETH high -> Buy WETH low -> WETH profit
// WETH in -> USDC out -> USDC in -> WETH out -> WETH profit
function flashSwap(
address pool0,
uint24 fee1,
address tokenIn,
address tokenOut,
uint amountIn
) external {
bool zeroForOne = tokenIn < tokenOut;
uint160 sqrtPriceLimitX96 = zeroForOne
? MIN_SQRT_RATIO + 1
: MAX_SQRT_RATIO - 1;
bytes memory data = abi.encode(
msg.sender,
pool0,
fee1,
tokenIn,
tokenOut,
amountIn,
zeroForOne
);

IUniswapV3Pool(pool0).swap(
address(this),
zeroForOne,
int(amountIn),
sqrtPriceLimitX96,
data
);
}

function uniswapV3SwapCallback(
int amount0,
int amount1,
bytes calldata data
) external {
(
address caller,
address pool0,
uint24 fee1,
address tokenIn,
address tokenOut,
uint amountIn,
bool zeroForOne
) = abi.decode(data, (address, address, uint24, address, address, uint, bool));

require(msg.sender == address(pool0), "not authorized");

uint amountOut;
if (zeroForOne) {
amountOut = uint(-amount1);
} else {
amountOut = uint(-amount0);
}

uint buyBackAmount = _swap(tokenOut, tokenIn, fee1, amountOut);

if (buyBackAmount >= amountIn) {
uint profit = buyBackAmount - amountIn;
IERC20(tokenIn).transfer(address(pool0), amountIn);
IERC20(tokenIn).transfer(caller, profit);
} else {
uint loss = amountIn - buyBackAmount;
IERC20(tokenIn).transferFrom(caller, address(this), loss);
IERC20(tokenIn).transfer(address(pool0), amountIn);
}
}

function _swap(
address tokenIn,
address tokenOut,
uint24 fee,
uint amountIn
) private returns (uint amountOut) {
IERC20(tokenIn).approve(address(router), amountIn);

ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: address(this),
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});

amountOut = router.exactInputSingle(params);
}
}

interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint deadline;
uint amountIn;
uint amountOutMinimum;
uint160 sqrtPriceLimitX96;
}

function exactInputSingle(
ExactInputSingleParams calldata params
) external payable returns (uint amountOut);
}

interface IUniswapV3Pool {
function swap(
address recipient,
bool zeroForOne,
int amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int amount0, int amount1);
}

interface IERC20 {
function totalSupply() external view returns (uint);

function balanceOf(address account) external view returns (uint);

function transfer(address recipient, uint amount) external returns (bool);

function allowance(address owner, address spender) external view returns (uint);

function approve(address spender, uint amount) external returns (bool);

function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);

event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}

interface IWETH is IERC20 {
function deposit() external payable;

function withdraw(uint amount) external;
}

Test with Foundry

  1. Copy and paste this into test folder in your foundry project
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "forge-std/console.sol";

import "../src/UniswapV3FlashSwap.sol";

contract UniswapV3FlashSwapTest is Test {
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

IWETH private weth = IWETH(WETH);

UniswapV3FlashSwap private uni = new UniswapV3FlashSwap();

function setUp() public {}

function testFlashSwap() public {
// USDC / WETH pool
address pool0 = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8;
uint24 fee0 = 3000;
address pool1 = 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640;
uint24 fee1 = 500;

// Approve WETH fee
uint wethMaxFee = 1e18;
weth.deposit{value: wethMaxFee}();
weth.approve(address(uni), wethMaxFee);

uint balBefore = weth.balanceOf(address(this));
uni.flashSwap(pool0, fee1, WETH, USDC, 10 * 1e18);
uint balAfter = weth.balanceOf(address(this));

if (balAfter >= balBefore) {
console.log("WETH profit", balAfter - balBefore);
} else {
console.log("WETH loss", balBefore - balAfter);
}
}
}
  1. Execute the following commands to run the test
FORK_URL=https://eth-mainnet.g.alchemy.com/v2/613t3mfjTevdrCwDl28CVvuk6wSIxRPi
forge test -vv --gas-report --fork-url $FORK_URL --match-path test/UniswapV3FlashSwapTest.test.sol